import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * ShowMap creates a BufferedImage that show the map and saves it to file every
 * 5 second
 * 
 * @author Peter Hohnloser
 */
public class ShowMap extends JPanel {

    private static final long serialVersionUID = 1L;
    private final BufferedImage map;
    private final JFrame frame;
    private final Thread task;
    // drawing and saving image size
    private final int imageWidth;
    private final int imageHeight;
    // scale up the image
    private final int scale = 4;
    // Time for saving the BufferedImage as image
    private final int saveImageTime = 5000;
    // Robot size in pixels
    private final int robotSize = 1;

    /**
     * Constructor for ShowMap
     * 
     * @param gridHeight
     *            the height of the grid (no. of rows)
     * @param gridWidth
     *            the width of the grid (no. of columns)
     */
    public ShowMap(final int gridHeight, final int gridWidth) {
        super(true);
        imageHeight = scale * gridHeight;
        imageWidth = scale * gridWidth;
        frame = new JFrame();
        // frame.setResizable(false);
        // make sure the program exits when the frame closes
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("Showing Map");
        frame.setSize(imageHeight + 17, imageWidth + 39);
        // This will center the JFrame in the middle of the screen
        frame.setLocationRelativeTo(null);
        map = new BufferedImage(gridHeight, gridWidth,
                BufferedImage.TYPE_INT_RGB);
        frame.setLayout(new BorderLayout());
        frame.add(this, BorderLayout.CENTER);

        // creating a gray BufferedImage
        final Color c = new Color(127, 127, 127);
        for (int col = 0; col < gridWidth; col++) {
            for (int row = 0; row < gridHeight; row++) {
                map.setRGB(row, col, c.getRGB());
            }
        }

        // updating the Gui
        updateUI();
        frame.setVisible(true);
        // creating a thread for saving the map as an image
        task = new Thread() {
            @Override
            public void run() {
                while (true) {
                    saveMap();
                    // System.out.println("Saving map.");
                    try {
                        Thread.sleep(saveImageTime);
                    } catch (final InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        task.start();
    }

    /**
     * Creates a new BufferedImage from a grid with float values between
     * 0.0-1.0, where 0.0 is black and 1.0 is white, with a grey scale in
     * between. Negative values are shown as gray. Call this Method after you
     * have updated the grid.
     * 
     * @param grid
     *            is the updated grid
     * @param robotRow
     *            is the current y-position (row) of the robot in the grid.
     * @param robotCol
     *            is the current x-position (column) of the robot translated to
     *            column in the grid.
     */
    public synchronized void updateMap(final float[][] grid,
            final int robotRow, final int robotCol) {
        Color c;
        for (int col = 0; col < grid.length; col++) {
            for (int row = 0; row < grid[0].length; row++) {
                float value = grid[col][row];
                // if value is <0 draw a gray pixel
                // else mapping the value between 0.0 - 1.0 where 0.0 is black
                // and 1.0 is white
                if (value < 0) {
                    c = new Color(0.5f, 0.5f, 0.5f);
                } else {
                    value = Math.abs(value - 1);
                    c = new Color(value, value, value);
                }
                // setting pixel color for pixel col and row
                map.setRGB(col, row, c.getRGB());
            }
        }
        // drawing a filled red Rectangle for the robot. Rectangle size is
        // 6x6
        final Graphics g = map.getGraphics();
        g.setColor(Color.RED);
        g.fillRect(robotRow - robotSize / 2, robotCol - robotSize / 2,
                robotSize, robotSize);
        // update the gui
        updateUI();
    }

    /**
     * Creates a new BufferedImage from a grid with integer values between 0 -
     * maxVal, where 0 is black and maxVal is white, with a grey scale in
     * between. Negative values are shown as gray. Call this Method after you
     * have updated the grid.
     * 
     * @param grid
     *            is the updated grid
     * @param maxVal
     *            is the max value that a grid cell can have (e.g. 15 in
     *            standard HIMM)
     * @param robotRow
     *            is the current y-position (row) of the robot in the grid.
     * @param robotCol
     *            is the current x-position (column) of the robot translated to
     *            column in the grid.
     */
    public synchronized void updateMap(final int[][] grid, final int maxVal,
            final int robotRow, final int robotCol) {
        // mapping the grid to a BufferedImage
        Color c;
        for (int col = 0; col < grid.length; col++) {
            for (int row = 0; row < grid[0].length; row++) {
                int value = grid[col][row];
                // if value is <0 draw a gray pixel
                // else mapping the value between 0 - 255 where 0 is black and
                // 255 is white
                if (value < 0) {
                    c = new Color(127, 127, 127);
                } else {
                    value = Math.abs(value * 255 / maxVal - 255);
                    c = new Color(value, value, value);
                }
                // setting pixel color for pixel col and row
                map.setRGB(col, row, c.getRGB());
            }

        }
        final Graphics g = map.getGraphics();
        // drawing a filled red Rectangle for the robot. Rectangle size is
        // 6x6
        g.setColor(Color.RED);
        g.fillRect(robotRow - robotSize / 2, robotCol - robotSize / 2,
                robotSize, robotSize);
        // update the gui
        updateUI();
    }

    /**
     * Method for saving the BufferedImage as a gif image
     */
    private synchronized void saveMap() {
        try {
            final File mapOutputfile = new File("map.gif");
            // Transforming the map BufferedImage to the size of image that is
            // shown
            final AffineTransform tx = new AffineTransform();
            tx.scale(scale, scale);
            final AffineTransformOp op = new AffineTransformOp(tx,
                    AffineTransformOp.TYPE_BILINEAR);
            final BufferedImage tmp = op.filter(map, null);
            ImageIO.write(tmp, "gif", mapOutputfile);

        } catch (final Exception e) {
            System.err.println("Couldn't save Map Image");
        }

    }

    @Override
    protected void paintComponent(final Graphics g) {
        // Transforming the map BufferedImage to the size of image that is shown
        final AffineTransform tx = new AffineTransform();
        tx.scale(2.5, 2.5);
        final BufferedImage after = new BufferedImage(imageHeight * scale,
                imageWidth * scale, BufferedImage.TYPE_INT_ARGB);
        final AffineTransformOp op = new AffineTransformOp(tx,
                AffineTransformOp.TYPE_BILINEAR);
        final BufferedImage tmp = op.filter(map, after);
        frame.setSize((int) (map.getWidth() * 2.5 + 17),
                (int) (map.getHeight() * 2.5 + 39));
        // drawing the transformed BufferedImage to the screen
        g.drawImage(tmp, 0, 0, this);
        g.dispose();
    }

}
